<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>JoyRL 文档</title>
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  <meta name="description" content="JoyRL 文档" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />

  <!-- ========== Docsify Theme (Light/Dark) ========== -->
  <link
    rel="stylesheet"
    media="(prefers-color-scheme: dark)"
    href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css"
  />
  <link
    rel="stylesheet"
    media="(prefers-color-scheme: light)"
    href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"
  />
  <!-- ========== PDF 链接新标签打开修复脚本 ========== -->
  <script>
    document.addEventListener('click', function (e) {
      const link = e.target.closest('a');
      if (!link) return;
      const href = link.getAttribute('href');
      if (href && /\.pdf($|\?)/i.test(href)) {
        e.preventDefault();
        let fixed = href.replace(/^#\//, '').replace(/^\.\//, '');
        if (!/^https?:/i.test(fixed) && !fixed.startsWith('/')) {
          const base = location.pathname.replace(/\/[^/]*$/, '/');
          fixed = base + fixed;
        }
        window.open(fixed, '_blank');
      }
    });
  </script>
  <style>
    /* =============== 基础主题调整 =============== */
    :root {
      /* 需要可以开启主题主色： */
      /* --theme-hue: 325; */
    }

    /* =============== Giscus 布局修复（与你原逻辑保持一致） =============== */
    #giscus-comments {
      max-width: 800px;
      width: 100%;
      margin: 2em auto;
      padding: 0;
      box-sizing: border-box;
      display: block;
      position: relative;
      z-index: 0;
    }

    .sidebar,
    .sidebar-toggle,
    .docsify-toc {
      position: relative;
      z-index: 20 !important;
    }
    
    /* 居中 Markdown 表格（整张表水平居中） */
    .markdown-section table {
      width: auto !important;              /* 不占满整行 */
      margin-left: auto !important;        /* 左边自动边距 */
      margin-right: auto !important;       /* 右边自动边距 */
      display: table !important;           /* 确保是表格显示模式 */
    }
    
    /* 表格外层容器居中 */
    article.markdown-section {
      text-align: left;  /* 保持文本左对齐 */
    }
    
    /* 表格本身居中 */
    .content table,
    section.content table,
    main table {
      margin-left: auto !important;
      margin-right: auto !important;
      width: auto !important;
    }
    
    /* 可选：表格标题也居中 */
    .markdown-section table caption { text-align: center; }

    .giscus,
    .giscus-frame {
      position: relative !important;
      z-index: 0 !important;
    }

    @media (max-width: 900px) {
      #giscus-comments {
        width: 100%;
        margin: 1.5em 0.5em;
      }
    }

    /* 优化 MathJax 显示 */
    mjx-container[jax="SVG"][display="true"] {
      text-align: center;
      margin: 1em 0;
    }

    /* 隐藏 docsify 主题默认的 > 箭头符号，只保留折叠插件的折叠图标 */
    .sidebar-nav li::before,
    .sidebar-nav li::after,
    .sidebar li::before,
    .sidebar li::after {
      content: '' !important;
      display: none !important;
      width: 0 !important;
      height: 0 !important;
      margin: 0 !important;
      padding: 0 !important;
      border: none !important;
    }
    
    /* 更具体地针对theme-simple样式 */
    .sidebar-nav ul li p::before,
    .sidebar-nav ul li a::before,
    .sidebar ul li p::before,
    .sidebar ul li a::before {
      content: '' !important;
      display: none !important;
    }
  </style>
</head>

<body>
  <div id="app"></div>
  
  <!-- 强制覆盖样式：隐藏默认箭头 -->
  <style>
    /* 最高优先级：隐藏所有默认的列表标记和箭头 */
    aside.sidebar .sidebar-nav ul li::before,
    aside.sidebar .sidebar-nav ul li p::before,
    aside.sidebar .sidebar-nav ul li a::before {
      content: '' !important;
      display: none !important;
      visibility: hidden !important;
    }
  </style>

  <!-- ===================== Docsify 核心配置 ===================== -->
  <script>
    window.$docsify = {
      /* 左上角标题与仓库入口（保持你的配置） */
      name: 'JoyRL',
      repo: 'https://github.com/datawhalechina/joyrl-book',

      /* 关键：启用侧边栏 + 相对路径模式 */
      loadSidebar: true,
      relativePath: true,   // ✅ 允许基于当前文档目录解析链接与资源

      /* 可选：限制侧边栏展示层级 */
      subMaxLevel: 4,

      /* 侧边栏折叠配置 */
      sidebarDisplayLevel: 1, // 设置为1，展示一级菜单，子菜单可折叠

      /* 你在用 docsify-latex，但我们也保留 docsify 原生 markdown 渲染配置入口 */
      latex: {
        inlineMath: [['$', '$'], ['\\(', '\\)']],
        displayMath: [['$$', '$$']],
      },

      /* 你后续的插件都统一放到 window.$docsify.plugins 数组里（见下方统一合并的插件） */
      plugins: []
    };
  </script>

  <!-- ===================== 自定义插件：图片路径修复 + MathJax 切换页面重置 + Giscus 注入 ===================== -->
  <script>
    /**
     * 这个统一插件做三件事（顺序非常重要）：
     * 1) beforeEach：在内容注入前
     *    - 清理/重置 MathJax 的计数与标签缓存（防止 label multiply-defined）
     *    - 将「相对图片路径 figs/...」修正为「基于当前 md 所在目录的路径」
     *      👉 支持 Markdown 语法 ![](figs/xxx.png) 与 HTML <img src="figs/...">
     * 2) doneEach：当页面渲染完
     *    - 重新 typeset 数学公式
     *    - 根据当前 hash 重新注入 Giscus（与你原先逻辑一致，但封装到 here）
     * 3) 其余功能（如返回顶部按钮）保持你原逻辑，并兼容 SPA 切换
     */
    window.$docsify.plugins = [].concat(window.$docsify.plugins || [], function (hook, vm) {

      /* -------------------- 工具函数：安全清理并重置 MathJax -------------------- */
      function resetMathJax() {
        if (!window.MathJax) return;

        // v3/v4 均支持的安全调用（存在则执行）
        try { MathJax.typesetClear && MathJax.typesetClear(); } catch (e) {}
        try { MathJax.texReset && MathJax.texReset(); } catch (e) {}

        // 进一步兜底（部分版本内部包名变动，这里做“尽力而为”的清空）
        try {
          if (MathJax._ && MathJax._.tex && MathJax._.tex.Tags) {
            // 部分版本为 reset / Reset，统一尝试
            (MathJax._.tex.Tags.reset && MathJax._.tex.Tags.reset()) ||
            (MathJax._.tex.Tags.Reset && MathJax._.tex.Tags.Reset());
          }
        } catch (e) {}
      }

      /* -------------------- 工具函数：修复内容中的图片相对路径 -------------------- */
      function rewriteImagePaths(content) {
        /**
         * 目标：允许在任意章节 md 里用最短写法
         *   - Markdown: ![](figs/a.png)  ->  自动变为  ch7/figs/a.png（如果当前在 ch7/xxx.md）
         *   - HTML:     <img src="figs/a.png"> -> 同上
         *
         * 注意：
         * - 不处理以 http(s):// 开头、以 data:、以 ./ 或 ../ 开头、以 / 开头的路径
         * - 只处理以 "figs/" 开头的「纯相对」短路径
         */
        var file = vm.route.file || ''; // 例如 "ch9/main.md"
        var dir  = file ? file.substring(0, file.lastIndexOf('/')) : ''; // "ch9"
        var prefix = dir ? (dir + '/') : '';

        // 1) 修复 HTML <img src="figs/..."> —— 保持其它属性不变，尽量严谨匹配
        content = content.replace(
          /(<img\b[^>]*?\bsrc\s*=\s*["'])(figs\/[^"']*)(["'][^>]*>)/gi,
          function (_, pre, src, post) {
            // 跳过特殊前缀
            if (/^(?:https?:|data:|\/|\.\/|\.\.\/)/i.test(src)) return pre + src + post;
            return pre + prefix + src + post; // 加上当前目录前缀
          }
        );

        // 2) 修复 Markdown ![alt](figs/xxx.png) —— 注意 alt 内可能有字符，非贪婪匹配
        content = content.replace(
          /(!\[[^\]]*?\]\()(\s*figs\/[^)]+)(\))/gi,
          function (_, pre, src, post) {
            // 跳过特殊前缀
            if (/^(?:\s*(?:https?:|data:|\/|\.\/|\.\.\/))/i.test(src)) return pre + src + post;
            // 去除开头的空格（如果有），再拼接前缀
            var cleaned = src.replace(/^\s+/, '');
            return pre + prefix + cleaned + post;
          }
        );

        return content;
      }

      /* -------------------- 生命周期：内容注入前 -------------------- */
      hook.beforeEach(function (content) {
        // ① 切换页面前：清理 MathJax，避免 label 多次定义
        resetMathJax();

        // ② 在 md 注入前，重写图片路径
        content = rewriteImagePaths(content);

        return content;
      });

      /* -------------------- 生命周期：页面渲染完成后 -------------------- */
      hook.doneEach(function () {
        // ③ 渲染完成后：重新 typeset 数学公式
        if (window.MathJax && MathJax.typesetPromise) {
          MathJax.typesetPromise();
        }

        // ④ 每次页面切换：重新渲染/注入 Giscus（与你原逻辑保持一致）
        try {
          var giscusId = 'giscus-comments';
          var section = document.querySelector('.markdown-section');

          // 移除旧的评论区
          var old = document.getElementById(giscusId);
          if (old) old.remove();

          // 基于当前 hash 作为讨论主题 key
          var hashPath = location.hash ? location.hash.replace(/^#\/?/, '') : 'home';

          if (section) {
            var giscusDiv = document.createElement('div');
            giscusDiv.id = giscusId;
            section.appendChild(giscusDiv);

            var script = document.createElement('script');
            script.src = 'https://giscus.app/client.js';
            script.setAttribute('data-repo', 'datawhalechina/joyrl-book');
            script.setAttribute('data-repo-id', 'R_kgDOJQd2zw');
            script.setAttribute('data-category', 'Q&A');
            script.setAttribute('data-category-id', 'DIC_kwDOJQd2z84Cwt-N');
            script.setAttribute('data-mapping', 'specific'); // 使用自定义 term
            script.setAttribute('data-term', hashPath);
            script.setAttribute('data-strict', '0');
            script.setAttribute('data-reactions-enabled', '1');
            script.setAttribute('data-emit-metadata', '0');
            script.setAttribute('data-input-position', 'bottom');
            script.setAttribute('data-theme', 'light');
            script.setAttribute('data-lang', 'zh-CN');
            script.setAttribute('crossorigin', 'anonymous');
            script.async = true;
            giscusDiv.appendChild(script);
          }
        } catch (e) {
          // 忽略 Giscus 注入失败
          console.warn('Giscus inject failed:', e);
        }

        // ⑤ 同步处理“返回顶部”按钮在 SPA 切换后的可见性
        var topBtn = document.getElementById('back-to-top');
        if (topBtn) {
          topBtn.style.display = (window.scrollY > 200) ? 'block' : 'none';
        }
      });
    });
  </script>

  <!-- ===================== Docsify 核心 & 常用插件 ===================== -->
  <script src="https://cdn.jsdelivr.net/npm/docsify@4"></script>
  <script src="https://cdn.jsdelivr.net/npm/docsify-to-pdf@1"></script>
  
  <script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/docsify-pagination/dist/docsify-pagination.min.js"></script>
  <!-- 侧边栏折叠插件 -->
  <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/sidebar.min.css" />
  <script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script>

  <!-- 自动编号的 MathJax 配置 -->
  <script>
    /**
     * MathJax v4 配置：
     * - 启用 ams/amsmath/bm 包
     * - tags: 'ams' 开启自动编号（配合我们在 beforeEach 的 reset 避免重复定义）
     * - 跳过 code/pre 等区块
     */
    window.MathJax = {
      tex: {
        inlineMath: [['$', '$'], ['\\(', '\\)']],
        displayMath: [['$$', '$$']],
        processEscapes: true,
        packages: {'[+]': ['ams','amsmath','bm']},
        tags: 'ams',         // ✅ 自动编号所有公式
        // tagSide: 'right',
        // tagIndent: '0em',
        options: {
          skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      }
    };
  </script>
  <script src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/docsify-latex@0"></script>
  <script src="//cdn.jsdelivr.net/npm/docsify-pdf-embed-plugin/dist/docsify-pdf-embed.min.js"></script>

  <!-- 代码复制按钮 -->
  <script src="https://cdn.jsdelivr.net/npm/docsify-copy-code"></script>

  <!-- =============== Giscus 评论系统 =============== -->
  <!-- <div id="giscus-comments">
    <script src="https://giscus.app/client.js"
      data-repo="datawhalechina/joyrl-book"
      data-repo-id="R_kgDOJQd2zw"
      data-category="Q&A"
      data-category-id="DIC_kwDOJQd2z84Cwt-N"
      data-mapping="pathname"
      data-strict="0"
      data-reactions-enabled="1"
      data-emit-metadata="0"
      data-input-position="bottom"
      data-theme="light"
      data-lang="zh-CN"
      crossorigin="anonymous"
      async>
    </script>
  </div> -->
  <!-- 返回顶部按钮 -->
  <button id="back-to-top" style="display:none;position:fixed;right:32px;bottom:32px;z-index:99;padding:10px 18px;border-radius:24px;background:#4f6ef7;color:#fff;border:none;box-shadow:0 2px 8px rgba(0,0,0,0.15);font-size:16px;cursor:pointer;transition:opacity 0.2s;">↑ 顶部</button>
  <script>
    // 返回顶部按钮逻辑
    var topBtn = document.getElementById('back-to-top');
    window.addEventListener('scroll', function() {
      topBtn.style.display = (window.scrollY > 200) ? 'block' : 'none';
    });
    topBtn.onclick = function() {
      window.scrollTo({top:0,behavior:'smooth'});
    };
    // Docsify SPA 页面切换后也要重置按钮
    if (window.$docsify) {
      if (!window.__topbtn_hooked) {
        window.__topbtn_hooked = true;
        window.$docsify.plugins = (window.$docsify.plugins || []);
        window.$docsify.plugins.push(function(hook) {
          hook.doneEach(function() {
            topBtn.style.display = (window.scrollY > 200) ? 'block' : 'none';
          });
        });
      }
    }
  </script>

</body>
</html>
